package rs

import (
	"errors"
	"fmt"
	"io"
	"os"
	"strconv"
	"youbei/dao"

	"github.com/segmentio/ksuid"

	"github.com/beego/beego/httplib"
)

// 定义上传接口响应的数据结构
type resAPIPacketup struct {
	Success int         `json:"success"`
	Msg     string      `json:"msg"`
	Result  interface{} `json:"result"`
}

// UploadByte 函数用于向指定的url上传字节数据
func UploadByte(url string, b []byte) error {
	res := resAPIPacketup{}
	err := httplib.Post(url).Body(b).ToJSON(&res)
	return err

}

// Filepackets 的 uploadfile 方法用于上传文件
func (c *Filepackets) uploadfile() error {
	res := resAPIPacketup{}
	req := httplib.Post(c.FileUploadURL)
	req.Param("username", c.UploadUser)
	req.Param("password", c.UploadPassword)
	req.Param("filename", c.FileName)
	req.Param("savedir", c.DstDir)
	req.Param("size", strconv.FormatInt(c.Size, 10))
	req.Param("packetnum", strconv.FormatInt(c.PacketNum, 10))
	err := req.ToJSON(&res)
	if err != nil {
		return err
	}
	if res.Success != 200 {
		return errors.New(res.Msg)
	}
	return nil
}

//ReadBigFile ...
func (c *RS) ReadBigFile() (*Filepackets, error) {
	fp, err := c.readMyFile()
	if err != nil {
		return nil, err
	}
	err = fp.uploadfile()
	if err != nil {
		return nil, err
	}
	return fp, nil
}

// Filepackets 结构体定义了文件分包的数据结构
type Filepackets struct {
	UploadFileServerID string

	FileUploadURL string
	FileName      string
	SrcDir        string
	SrcFilePath   string
	Size          int64

	DstDir      string
	DstFilePath string

	UploadUser     string
	UploadPassword string

	PacketUploadURL string
	Packets         map[int]Packet
	PacketSize      int64
	PacketNum       int64

	UploadDoneURL string
}

// Packet 结构体定义了文件切片的数据结构
type Packet struct {
	SortID     int
	Packetpath string
	Offset     int64
	Error      error
}

const packetSize = int64(20 * 1024 * 1024) // 定义分包大小

// readMyFile 方法用于读取文件并分包
func (c *RS) readMyFile() (*Filepackets, error) {
	// 创建一个新的Filepackets对象
	filepackets := new(Filepackets)
	filepackets.FileName = c.FileName
	filepackets.SrcFilePath = c.SrcFilePath
	filepackets.SrcDir = c.SrcDir

	// 获取源文件的信息
	fi, err := os.Stat(filepackets.SrcFilePath)
	if err != nil {
		return nil, err
	}

	filepackets.Size = fi.Size()                                                         // 获取文件大小
	filepackets.PacketSize = packetSize                                                  // 设置每个分包的大小
	filepackets.PacketNum = calculatePacketNum(filepackets.Size, filepackets.PacketSize) // 计算分包数量

	// 创建分包
	filepackets.Packets = makePackets(filepackets.PacketNum, filepackets.PacketSize)                    // 设置分包
	filepackets.UploadFileServerID = ksuid.New().String()                                               // 设置上传服务器的ID
	baseURL := fmt.Sprintf("http://%s:%d/upload", c.Host, c.Port)                                       // 设置基础的URL
	filepackets.FileUploadURL = fmt.Sprintf("%s/file/%s", baseURL, filepackets.UploadFileServerID)      // 设置文件上传的URL
	filepackets.PacketUploadURL = fmt.Sprintf("%s/packet/%s/", baseURL, filepackets.UploadFileServerID) // 设置分包上传的URL
	filepackets.UploadDoneURL = fmt.Sprintf("%s/done/", baseURL)                                        // 设置上传完成的URL
	filepackets.DstDir = c.DstDir                                                                       // 设置目标文件夹
	filepackets.DstFilePath = c.DstFilePath                                                             // 设置目标文件路径
	filepackets.UploadUser = c.UploadUser                                                               // 设置上传用户
	filepackets.UploadPassword = c.UploadPassword                                                       // 设置上传密码

	return filepackets, nil
}

// calculatePacketNum 计算需要的分包数量
func calculatePacketNum(size, packetSize int64) int64 {
	// 基本的分包数量
	packetNum := size / packetSize
	// 如果文件大小不能被分包大小整除，分包数量加一
	if size%packetSize > 0 {
		packetNum++
	}
	return packetNum
}

// makePackets 创建分包
func makePackets(packetNum, packetSize int64) map[int]Packet {
	fps := map[int]Packet{}
	// 对每个分包进行设置
	for i := int64(0); i < packetNum; i++ {
		packet := Packet{
			SortID: int(i),         // 分包的序号
			Offset: i * packetSize, // 分包的偏移量
		}
		fps[packet.SortID] = packet
	}
	return fps
}

// CreatePacket 方法用于创建文件切片
func (c *Filepackets) CreatePacket(file *os.File, v dao.YsPacket) error {
	// 定义每次从文件读取的缓冲区大小为 1MB
	const bufferSize = 1024 * 1024
	buf := make([]byte, bufferSize)
	remaining := c.PacketSize // 初始化剩余要读取的文件大小

	// 当还有剩余需要读取的文件大小时，继续循环
	for remaining > 0 {
		// 如果剩余大小小于缓冲区大小，则本次读取的大小为剩余大小；否则，为缓冲区大小
		bytesToRead := min(remaining, bufferSize)
		// 从文件的指定位置读取指定长度的数据
		nr, err := file.ReadAt(buf[:bytesToRead], v.Offset)
		// 如果读取出现错误，并且这个错误不是因为到达文件末尾引起的，那么直接返回这个错误
		if err != nil && err != io.EOF {
			return err
		}
		// 如果读取到的数据长度大于 0，表示有数据需要上传
		if nr > 0 {
			// 上传读取到的数据
			err = UploadByte(c.PacketUploadURL+strconv.FormatInt(v.Offset, 10), buf[:nr])
			// 如果上传过程中发生错误，则返回这个错误
			if err != nil {
				return err
			}
		}
		// 更新剩余需要读取的文件大小和下一次读取的偏移位置
		remaining -= int64(nr)
		v.Offset += int64(nr)
		// 当遇到 EOF 错误并且没有读取到数据时，跳出循环
		if err == io.EOF && nr == 0 {
			break
		}
	}
	// 如果所有数据都处理完毕且无错误，返回nil表示处理成功
	return nil
}

// min 函数返回两个int64类型数值中的较小值
func min(a, b int64) int64 {
	if a < b {
		return a
	}
	return b
}

// UploadDone 方法用于通知文件上传完成
func (c *Filepackets) UploadDone(status string) error {
	res := resAPIPacketup{}
	err := httplib.Post(c.UploadDoneURL+c.UploadFileServerID).Param("status", status).ToJSON(&res)
	return err
}
